<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Middleware;

use Hyperf\Context\Context;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;

/**
 * 全局请求日志中间件
 * 记录所有 HTTP 请求的详细信息
 */
class RequestLogMiddleware implements MiddlewareInterface
{
    protected LoggerInterface $logger;

    public function __construct(
        protected ContainerInterface $container,
        protected StdoutLoggerInterface $stdoutLogger
    ) {
        // 使用专门的请求日志记录器
        $this->logger = $container->get(LoggerFactory::class)->get('request', 'request');
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 记录请求开始时间
        $startTime = microtime(true);
        
        // 获取请求信息
        $method = $request->getMethod();
        $uri = $request->getUri();
        $path = $uri->getPath();
        $queryParams = $request->getQueryParams();
        $headers = $request->getHeaders();
        $clientIp = $this->getClientIp($request);
        
        // 获取请求体数据
        $body = $this->getRequestBody($request);
        
        // 记录请求信息
        $requestId = $this->generateRequestId();
        Context::set('request_id', $requestId);
        
        // 构建日志数据
        $logData = [
            'request_id' => $requestId,
            'method' => $method,
            'uri' => (string) $uri,
            'path' => $path,
            'query' => $queryParams,
            'body' => $body,
            'headers' => $this->filterHeaders($headers),
            'client_ip' => $clientIp,
            'user_agent' => $request->getHeaderLine('User-Agent'),
            'request_time' => date('Y-m-d H:i:s', (int) $startTime),
            'timestamp' => $startTime,
        ];
        
        // 记录请求日志
        $this->logger->info('HTTP Request', $logData);
        
        // 处理请求
        try {
            $response = $handler->handle($request);
            
            // 计算请求耗时
            $endTime = microtime(true);
            $duration = round(($endTime - $startTime) * 1000, 2); // 毫秒
            
            // 记录响应信息
            $responseLogData = [
                'request_id' => $requestId,
                'method' => $method,
                'path' => $path,
                'status_code' => $response->getStatusCode(),
                'duration_ms' => $duration,
                'response_time' => date('Y-m-d H:i:s', (int) $endTime),
            ];
            
            // 根据状态码决定日志级别
            if ($response->getStatusCode() >= 500) {
                $this->logger->error('HTTP Response', $responseLogData);
            } elseif ($response->getStatusCode() >= 400) {
                $this->logger->warning('HTTP Response', $responseLogData);
            } else {
                $this->logger->info('HTTP Response', $responseLogData);
            }
            
            return $response;
        } catch (\Throwable $throwable) {
            // 记录异常信息
            $endTime = microtime(true);
            $duration = round(($endTime - $startTime) * 1000, 2);
            
            $this->logger->error('HTTP Request Failed', [
                'request_id' => $requestId,
                'method' => $method,
                'path' => $path,
                'duration_ms' => $duration,
                'exception' => get_class($throwable),
                'message' => $throwable->getMessage(),
                'file' => $throwable->getFile(),
                'line' => $throwable->getLine(),
            ]);
            
            throw $throwable;
        }
    }

    /**
     * 获取请求体数据
     */
    protected function getRequestBody(ServerRequestInterface $request): array|string
    {
        $contentType = $request->getHeaderLine('Content-Type');
        
        // JSON 请求
        if (str_contains($contentType, 'application/json')) {
            $body = (string) $request->getBody();
            if ($body) {
                $decoded = json_decode($body, true);
                return is_array($decoded) ? $this->filterSensitiveData($decoded) : $body;
            }
            return [];
        }
        
        // 表单请求
        if (str_contains($contentType, 'application/x-www-form-urlencoded') || 
            str_contains($contentType, 'multipart/form-data')) {
            $parsedBody = $request->getParsedBody();
            return is_array($parsedBody) ? $this->filterSensitiveData($parsedBody) : [];
        }
        
        // 其他类型，获取原始内容（限制大小）
        $body = (string) $request->getBody();
        if (strlen($body) > 1000) {
            return substr($body, 0, 1000) . '... (truncated)';
        }
        
        return $body ?: [];
    }

    /**
     * 过滤敏感数据
     * 将密码、token 等敏感字段的值替换为 ***
     */
    protected function filterSensitiveData(array $data): array
    {
        $sensitiveKeys = [
            'password',
            'password_confirmation',
            'old_password',
            'new_password',
            'token',
            'access_token',
            'refresh_token',
            'api_key',
            'secret',
            'credit_card',
            'cvv',
            'ssn',
        ];
        
        foreach ($data as $key => $value) {
            $lowerKey = strtolower($key);
            
            // 如果是敏感字段，替换为 ***
            foreach ($sensitiveKeys as $sensitiveKey) {
                if (str_contains($lowerKey, $sensitiveKey)) {
                    $data[$key] = '***';
                    continue 2;
                }
            }
            
            // 递归处理嵌套数组
            if (is_array($value)) {
                $data[$key] = $this->filterSensitiveData($value);
            }
        }
        
        return $data;
    }

    /**
     * 过滤请求头
     * 移除或脱敏敏感的请求头
     */
    protected function filterHeaders(array $headers): array
    {
        $sensitiveHeaders = [
            'authorization',
            'cookie',
            'x-api-key',
            'x-auth-token',
        ];
        
        $filtered = [];
        foreach ($headers as $name => $values) {
            $lowerName = strtolower($name);
            
            if (in_array($lowerName, $sensitiveHeaders)) {
                $filtered[$name] = ['***'];
            } else {
                $filtered[$name] = $values;
            }
        }
        
        return $filtered;
    }

    /**
     * 获取客户端真实 IP
     */
    protected function getClientIp(ServerRequestInterface $request): string
    {
        // 尝试从各种代理头获取真实 IP
        $headers = [
            'X-Forwarded-For',
            'X-Real-IP',
            'X-Client-IP',
            'CF-Connecting-IP', // Cloudflare
            'True-Client-IP',   // Akamai
        ];
        
        foreach ($headers as $header) {
            if ($request->hasHeader($header)) {
                $ip = $request->getHeaderLine($header);
                // X-Forwarded-For 可能包含多个 IP，取第一个
                if (str_contains($ip, ',')) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                if ($this->isValidIp($ip)) {
                    return $ip;
                }
            }
        }
        
        // 从服务器参数获取
        $serverParams = $request->getServerParams();
        return $serverParams['remote_addr'] ?? 'unknown';
    }

    /**
     * 验证 IP 地址是否有效
     */
    protected function isValidIp(string $ip): bool
    {
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }

    /**
     * 生成唯一的请求 ID
     */
    protected function generateRequestId(): string
    {
        return uniqid('req_', true) . '_' . bin2hex(random_bytes(8));
    }
}

